/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Events Module / Extensions:	Provides a "home" for event-related functionality and defines some event extensions which qualize browser differences among several proprietary events
*
*	Requires:
*	Gimme.query.js
*	Gimme.core.js
*
*/

Gimme.Events = new function()
{
	var handlerHash = {};
	var capturingElem = null;
	var rerouteFn = null;
	
	this.capture_mouse = function(_elem)
	{
		Gimme.Events.release_mouse();
		capturingElem = _elem;
		
		if (typeof _elem.setCapture !== 'undefined')
		{
			_elem.setCapture();
		}
		else
		{
			rerouteFn = function(e)
			{
				var synthEvent, scrollPos;
				if (Gimme.Browser.is_gecko || Gimme.Browser.is_opera)
				{
					synthEvent = document.createEvent('MouseEvents');
					synthEvent.initMouseEvent(
						e.type							// type
						,e.bubbles						// can bubble?
						,e.cancelable					// cancelable?
						,window							// always window here
						,e.detail						// mouse click count
						,e.screenX						// event's screen x coordinate (copied from the passed in event)
						,e.screenY						// event's screen y coordinate (copied from the passed in event)
						,e.clientX						// event's client x coordinate (copied from the passed in event)
						,e.clientY						// event's client y coordinate (copied from the passed in event)
						,e.ctrlKey						// whether or not the ctrl key was pressed during the event (copied from the passed in event)
						,e.altKey						// whether or not the alt key was pressed during the event (copied from the passed in event)
						,e.shiftKey						// whether or not the shift key was pressed during the event (copied from the passed in event)
						,e.metaKey						// whether or not the meta key was pressed during the event (copied from the passed in event)
						,e.button						// indicates which button (if any) caused the mouse event (copied from the passed in event)
						,e.relatedTarget				// relatedTarget (only applicable for 'mouseover' and 'mouseout' events)
					);
					
					scrollPos = Gimme.Screen.get_scroll_pos();
					synthEvent.__defineGetter__('pageX', function() { return this.clientX + scrollPos.x; });
					synthEvent.__defineGetter__('pageY', function() { return this.clientY + scrollPos.y; });
				}
				else
				{
					synthEvent = e;
				}
				
				document.removeEventListener(e.type, rerouteFn, true);
				synthEvent.captureTarget = e.target;
				_elem.dispatchEvent(synthEvent);
				if (rerouteFn !== null)
				{
					document.addEventListener(e.type, rerouteFn, true);
				}
				delete synthEvent.captureTarget;
				
				e.stopPropagation();
			}
			
			document.addEventListener('mouseover', rerouteFn, true);
			document.addEventListener('mouseout', rerouteFn, true);
			document.addEventListener('mousemove', rerouteFn, true);
			document.addEventListener('mouseup', rerouteFn, true);
			document.addEventListener('mousedown', rerouteFn, true);
			document.addEventListener('click', rerouteFn, true);
			document.addEventListener('dblclick', rerouteFn, true);
		}
		
		return this;
	};
	
	this.release_mouse = function()
	{
		if (capturingElem !== null)
		{
			if (typeof capturingElem.releaseCapture !== 'undefined')
			{
				capturingElem.releaseCapture();
			}
			else
			{
				document.removeEventListener('mouseover', rerouteFn, true);
				document.removeEventListener('mouseout', rerouteFn, true);
				document.removeEventListener('mousemove', rerouteFn, true);
				document.removeEventListener('mouseup', rerouteFn, true);
				document.removeEventListener('mousedown', rerouteFn, true);
				document.removeEventListener('click', rerouteFn, true);
				document.removeEventListener('dblclick', rerouteFn, true);
			}
			
			capturingElem = rerouteFn = null;
		}
		
		return this;
	};

	this.get_capture_target = function(_evt)
	{
		return _evt.captureTarget || _evt.srcElement || _evt.target;
	};
	
	this.Pseudo =
	{
		'mouseenter':
		{
			wire: function(gim, handler, useCapture, listening)
			{
				var wireFn = listening ? Gimme.ext.add_event : Gimme.ext.remove_event;
				wireFn.call(gim, 'mouseover', mouseEnter(handler), useCapture, true);
			},
			add: function(gim, handler, useCapture)
			{
				Gimme.Events.Pseudo.mouseenter.wire(gim, handler, useCapture, true);
			},
			remove: function(gim, handler, useCapture)
			{
				Gimme.Events.Pseudo.mouseenter.wire(gim, handler, useCapture, false);
			}
		},
		
		'mouseleave':
		{
			wire: function(gim, handler, useCapture, listening)
			{
				var wireFn = listening ? Gimme.ext.add_event : Gimme.ext.remove_event;
				wireFn.call(gim, 'mouseout', mouseEnter(handler), useCapture, true);
			},
			add: function(gim, handler, useCapture)
			{
				Gimme.Events.Pseudo.mouseleave.wire(gim, handler, useCapture, true);
			},
			remove: function(gim, handler, useCapture)
			{
				Gimme.Events.Pseudo.mouseleave.wire(gim, handler, useCapture, false);
			}
		},
		
		'mousewheel':
		{
			wire: function(gim, handler, useCapture, listening)
			{
				var wireFn = listening ? Gimme.ext.add_event : Gimme.ext.remove_event;
				
				var eventName = 'mousewheel';
				var proxy = handler;
				if (Gimme.Browser.is_gecko)
				{
					eventName = 'DOMMouseScroll';
					proxy = mouseWheel(handler);
				}
				
				wireFn.call(gim, eventName, proxy, useCapture, true);			
			},
			add: function(gim, handler, useCapture)
			{
				Gimme.Events.Pseudo.mousewheel.wire(gim, handler, useCapture, true);				
			},
			remove: function(gim, handler, useCapture)
			{
				Gimme.Events.Pseudo.mousewheel.wire(gim, handler, useCapture, false);
			}
		}
	};
	
	// Private helper functions
	
	function isAnAncestorOf(ancestor, descendant, generation)
	{
		if (ancestor === descendant)
		{
			return false;
		}
		
		var gen = 0;
		while (descendant && descendant != ancestor)
		{
			gen++;
			descendant = descendant.parentNode;
		}
		
		generation = generation || gen;
		return descendant === ancestor && generation === gen;		
	}
	
	function mouseEnter(handler)
	{	
		var key = Gimme.Helper.get_guid(handler);
		var proxy = handlerHash[key];
		if (!proxy)
		{
			proxy = handlerHash[key] = function(e)
			{
				var relTarget = e.relatedTarget;
				if (!relTarget || this === relTarget || isAnAncestorOf(this, relTarget))
				{
					return;
				}
		
				handler.call(this, e);
			};
		}
		return proxy;
	}
	
	function mouseWheel(handler)
	{
		var key = Gimme.Helper.get_guid(handler);
		var proxy = handlerHash[key];
		if (!proxy)
		{
			proxy = handlerHash[key] = function(e)
			{
				// Gecko's "detail" is in terms of lines scrolled (typically 3), and has a 
				// sign opposite that of IE's wheelDelta (which uses 120-unit increments).
				// This maps back into IE's style of units.
				e.wheelDelta = -40 * e.detail;
				handler.call(this, e);
				e.wheelDelta = null;
			}
		}
		return proxy;
	}
};

